﻿#ifndef ___PCLIB_S_USB_HID___
#define ___PCLIB_S_USB_HID___


#include "../m_core/m_core.hpp"
#include "../m_format/s_json.hpp"

#if defined(PCOS_WIN)
    typedef struct _PC_HIDD_ATTRIBUTES {
        ULONG   Size; 
        USHORT  VendorID;
        USHORT  ProductID;
        USHORT  VersionNumber;
    } PC_HIDD_ATTRIBUTES, *PPC_HIDD_ATTRIBUTES;

    typedef struct _PC_HIDP_CAPS
    {
        USHORT   Usage;
        USHORT   UsagePage;
        USHORT   InputReportByteLength;
        USHORT   OutputReportByteLength;
        USHORT   FeatureReportByteLength;
        USHORT   Reserved[17];

        USHORT   NumberLinkCollectionNodes;

        USHORT   NumberInputButtonCaps;
        USHORT   NumberInputValueCaps;
        USHORT   NumberInputDataIndices;

        USHORT   NumberOutputButtonCaps;
        USHORT   NumberOutputValueCaps;
        USHORT   NumberOutputDataIndices;

        USHORT   NumberFeatureButtonCaps;
        USHORT   NumberFeatureValueCaps;
        USHORT   NumberFeatureDataIndices;
    } PC_HIDP_CAPS, *PPC_HIDP_CAPS;

    typedef void* PPC_HIDP_PREPARSED_DATA;

    typedef void   (__stdcall *PC_HidD_GetHidGuid_)(LPGUID HidGuid);
    typedef BOOLEAN(__stdcall *PC_HidD_GetAttributes_)(HANDLE device, PPC_HIDD_ATTRIBUTES attrib);
    typedef BOOLEAN(__stdcall *PC_HidD_GetSerialNumberString_)(HANDLE device, PVOID buffer, ULONG buffer_len);
    typedef BOOLEAN(__stdcall *PC_HidD_GetManufacturerString_)(HANDLE handle, PVOID buffer, ULONG buffer_len);
    typedef BOOLEAN(__stdcall *PC_HidD_GetProductString_)(HANDLE handle, PVOID buffer, ULONG buffer_len);
    typedef BOOLEAN(__stdcall *PC_HidD_SetFeature_)(HANDLE handle, PVOID data, ULONG length);
    typedef BOOLEAN(__stdcall *PC_HidD_GetFeature_)(HANDLE handle, PVOID data, ULONG length);
    typedef BOOLEAN(__stdcall *PC_HidD_GetIndexedString_)(HANDLE handle, ULONG string_index, PVOID buffer, ULONG buffer_len);
    typedef BOOLEAN(__stdcall *PC_HidD_GetPreparsedData_)(HANDLE handle, PPC_HIDP_PREPARSED_DATA *preparsed_data);
    typedef BOOLEAN(__stdcall *PC_HidD_FreePreparsedData_)(PPC_HIDP_PREPARSED_DATA preparsed_data);
    typedef NTSTATUS(__stdcall *PC_HidP_GetCaps_)(PPC_HIDP_PREPARSED_DATA preparsed_data, PC_HIDP_CAPS *caps);
    typedef BOOLEAN(__stdcall *PC_HidD_SetNumInputBuffers_)(HANDLE handle, ULONG number_buffers);


#endif


/**
*@brief  USB-HID连接对象
*/
class CPCUSB_HID : CPCNoCopyable
{
public:
	CPCUSB_HID()
	{
#if defined(PCOS_WIN)	
        PC_HidD_GetAttributes = NULL;
        PC_HidD_GetSerialNumberString = NULL;
        PC_HidD_GetManufacturerString = NULL;
        PC_HidD_GetProductString = NULL;
        PC_HidD_SetFeature = NULL;
        PC_HidD_GetFeature = NULL;
        PC_HidD_GetIndexedString = NULL;
        PC_HidD_GetPreparsedData = NULL;
        PC_HidD_FreePreparsedData = NULL;
        PC_HidP_GetCaps = NULL;
        PC_HidD_SetNumInputBuffers = NULL;

        m_hHIDDll = LoadLibraryA("HID.dll");
        if (m_hHIDDll)
        {
            PC_HidD_GetHidGuid = (PC_HidD_GetHidGuid_)GetProcAddress(m_hHIDDll, "HidD_GetHidGuid");
            PC_HidD_GetPreparsedData = (PC_HidD_GetPreparsedData_)GetProcAddress(m_hHIDDll, "HidD_GetPreparsedData");
            PC_HidP_GetCaps = (PC_HidP_GetCaps_)GetProcAddress(m_hHIDDll, "HidP_GetCaps");
            PC_HidD_FreePreparsedData = (PC_HidD_FreePreparsedData_)GetProcAddress(m_hHIDDll, "HidD_FreePreparsedData");
        }
        m_hDevice = NULL;
        m_InputReportByteLength = 0;
        m_OutputReportByteLength = 0;
#endif
    }
	~CPCUSB_HID()
    {
        XClose();
#if defined(PCOS_WIN)	
        if (m_hHIDDll)
        {
            FreeLibrary(m_hHIDDll);
            m_hHIDDll = NULL;
        }
#endif
    }

    /**
    *@brief     使用设置的属性连接
    *@param     pszPortName[in]		端口名，通过EnumDevice接口可得到
    *@param     pszPropsJson[in]    指定属性，预留
    *@return    是否成功。
    */
    CPCResult<> XConnect(const char* pszPortName, const char* pszPropsJson)
    {
        CPCResult<> result;
        if (pszPortName == NULL || pszPortName[0] == 0)
        {
            return result.SetFail("port name invalid");
        }
        XClose();

#if defined(PCOS_WIN)
        //再打开设备
        m_hDevice = CreateFileA(pszPortName,
            GENERIC_READ | GENERIC_WRITE,
            0,//FILE_SHARE_READ | FILE_SHARE_WRITE, 
            NULL,
            OPEN_EXISTING,
            FILE_FLAG_OVERLAPPED,
            NULL);
        if (m_hDevice == INVALID_HANDLE_VALUE)
        {
            return result.SetSystemFail();
        }
        if (PC_HidD_GetPreparsedData == NULL || PC_HidP_GetCaps == NULL || PC_HidD_FreePreparsedData == NULL)
        {
            return result.SetFail("Load HID.dll fail");
        }

        //HID类型
        PPC_HIDP_PREPARSED_DATA	ParsedData;
        PC_HIDP_CAPS			Capabilities;
        PC_HidD_GetPreparsedData(m_hDevice, &ParsedData);
        PC_HidP_GetCaps(ParsedData, &Capabilities);
        PC_HidD_FreePreparsedData(ParsedData);

        m_InputReportByteLength = Capabilities.InputReportByteLength;
        m_OutputReportByteLength = Capabilities.OutputReportByteLength;
        if (m_InputReportByteLength < 2 || m_OutputReportByteLength < 2 || m_InputReportByteLength >= PC_MAXPACK || m_OutputReportByteLength >= PC_MAXPACK)
        {
            return result.SetFail("m_InputReportByteLength(%d) || m_OutputReportByteLength(%d) invalid.", (int)m_InputReportByteLength, (int)m_OutputReportByteLength);
        }
#else
        result.SetFail("not supported");
#endif
        return result;
    }


    /**
    *@brief      主动执行关闭的动作
    *            执行这个动作会触发OnClosed事件
    */
    void XClose()
    {
#if defined(PCOS_WIN)
        PCCloseHandle(m_hDevice);
#endif
    }

    /**
    *@brief     发送数据动作
    *@param     pdata[in]			数据
    *@param     length[in]			数据长度
    *@param     nTimeoutMs[in]		超时时间毫秒，<0代表永不超时
    *@return    是否成功
    */
    CPCResult<> XSend(const void* pdata, size_t length, int nWaitMs = -1)
    {
        CPCResult<> result;
        if (pdata == NULL)
        {
            return result.SetFail("data invalid");
        }
        char* buf = (char*)pdata;
#if defined(PCOS_WIN)
        if (m_hDevice == NULL || m_hDevice == INVALID_HANDLE_VALUE)
        {
            return result.SetFail("device not open");
        }
        //数据按帧发送，帧的第一个字节是0x00的报告字节
        USHORT  usPackLen = this->m_OutputReportByteLength - 1;
        BYTE    szFrameData[PC_MAXPACK] = { 0 };
        OVERLAPPED    HidOverlapped;
        size_t nTimes = (length % usPackLen) ? (length / usPackLen + 1) : (length / usPackLen);
        for (size_t i = 0; i < nTimes; i++)
        {
            memset(szFrameData, 0x00, sizeof(szFrameData));
            memcpy(szFrameData + 1, buf + i*usPackLen, (i == nTimes - 1) ? length % usPackLen : usPackLen);

            HidOverlapped.hEvent = CreateEventA(NULL, FALSE, FALSE, NULL);
            HidOverlapped.Offset = 0;
            HidOverlapped.OffsetHigh = 0;
            PC_DEFER([&HidOverlapped](){ CloseHandle(HidOverlapped.hEvent);  });

            DWORD WriteLen = 0;
            auto bRet = WriteFile(m_hDevice, szFrameData, usPackLen + 1, &WriteLen, &HidOverlapped);
            if (!bRet)
            {
                int nErrCode = PCGetLastError();
                if (nErrCode != ERROR_IO_PENDING)
                {
                    return result.SetSystemFail(nErrCode);
                }
                if (WaitForSingleObject(HidOverlapped.hEvent, nWaitMs) != WAIT_OBJECT_0)
                {
                    CancelIo(m_hDevice);
                    return result.SetFail("timeout:%d ms", nWaitMs);
                }

                GetOverlappedResult(m_hDevice, &HidOverlapped, &WriteLen, TRUE);
            }
        }
#else
        result.SetFail("not supported");
#endif
        return result;
    }

    /**
    *@brief     从接收数据流中读取一帧，超时则直接关闭
    *@param     nWaitMs[in]			等待多少毫秒后超时。<0无限等待 0马上返回 >0为超时时间。
    *@return    是否成功，成功后数据在CPCBuffer中
    */
    CPCResult<CPCBuffer> XRecvFrame(int nWaitMs)
    {
        CPCResult<CPCBuffer> result;
        auto& retBuffer = result.Get();
#if defined(PCOS_WIN)
        retBuffer.reserve(m_InputReportByteLength);
        if (m_hDevice == NULL || m_hDevice == INVALID_HANDLE_VALUE)
        {
            return result.SetFail("device not open");
        }
        
        OVERLAPPED   HidOverlapped;
        memset(&HidOverlapped, 0, sizeof(OVERLAPPED));
        HidOverlapped.hEvent = CreateEventA(NULL, TRUE, FALSE, NULL);
        PC_DEFER([&HidOverlapped](){ CloseHandle(HidOverlapped.hEvent);  });

        BYTE  szFrameData[PC_MAXPACK] = { 0 };
        DWORD nRecvDataLen = 0;
        BOOL bRet = ReadFile(m_hDevice, szFrameData, m_InputReportByteLength, &nRecvDataLen, &HidOverlapped);
        if (!bRet)
        {
            int nErrCode = PCGetLastError();
            if (GetLastError() != ERROR_IO_PENDING)
            {
                return result.SetSystemFail(nErrCode);
            }
            if (WaitForSingleObject(HidOverlapped.hEvent, nWaitMs) != WAIT_OBJECT_0)
            {
                CancelIo(m_hDevice);
                return result.SetFail("timeout:%d ms", nWaitMs);
            }
            
            GetOverlappedResult(m_hDevice, &HidOverlapped, &nRecvDataLen, TRUE);
        }
        //第一个字节的0x00报告字节不要
        retBuffer.append((const char*)(szFrameData + 1), nRecvDataLen - 1);
#else
        result.SetFail("not supported");
#endif
        return result;
    }

    /**
    *@brief     【此函数暂未实现】从接收数据流中读取数据，超时则直接关闭
    *@param     nWaitMs[in]			等待多少毫秒后超时。<0无限等待 0马上返回 >0为超时时间。
    *@param     nRecvSize[in]		要接收的数据长度
    *@param     bForceBufFull [in]	bForceBufFull = false   一般情况：超时/出错/用户缓冲区读满/有数据后间隔100毫秒无数据则返回
    *								bForceBufFull = true    例外情况：强制用户缓冲区读满则才返回，或者出现出错/超时。
    *@return    是否成功，成功后数据在CPCBuffer中
    */
    CPCResult<CPCBuffer> XRecv(int nWaitMs, size_t nRecvSize, bool bForceBufFull = false)
    {
        CPCResult<CPCBuffer> result;
        if (nRecvSize == 0)
        {
            return result.SetFail("nRecvSize invalid");
        }
        auto& retBuffer = result.Get();
        retBuffer.reserve(nRecvSize);
#if defined(PCOS_WIN)
        if (m_hDevice == NULL || m_hDevice == INVALID_HANDLE_VALUE)
        {
            return result.SetFail("device not open");
        }
        //TODO
        result.SetFail("not supported");
#else
        result.SetFail("not supported");
#endif
        return result;
    }



public:
    /**
    *@brief     枚举USB HID设备
    *@param     pszFilter[in]		过滤（如： vid_0505&pid_5050），只返回包括过滤字符串的设备。可以为空，代表返回所有。 
    *@return    是否成功。map.first为端口名；map.second为端口描述。
    */
    static CPCResult<std::unordered_map<std::string, std::string> > EnumDevice(const char* pszFilter = NULL)
    {
        CPCResult<std::unordered_map<std::string, std::string> > result;
        auto& umDevices = result.Get();
#if defined(PCOS_WIN)
        // 取得一个该GUID相关的设备信息集句柄
        //GUID lpGuid;
        //PC_HidD_GetHidGuid(&lpGuid);
        GUID lpGuid = { 0x4d1e55b2, 0xf16f, 0x11cf, { 0x88, 0xcb, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30 } };

        //初始化环境
        HDEVINFO hDevInfo = SetupDiGetClassDevsA(&lpGuid,//class GUID　
            NULL,//无关键字　
            NULL,//不指定父窗口句柄　
            DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);//目前存在的设备
        if (hDevInfo == INVALID_HANDLE_VALUE)
        {
            return result.SetSystemFail();
        }
        PC_DEFER([&hDevInfo](){ SetupDiDestroyDeviceInfoList(hDevInfo); });

        //枚举USB设备
        DWORD dwDetDataSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_A) + MAX_PATH;
        SP_DEVICE_INTERFACE_DETAIL_DATA_A *pDetData = (SP_DEVICE_INTERFACE_DETAIL_DATA_A *)new char[dwDetDataSize];
        pDetData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_A);
        PC_DEFER([&pDetData](){ PCDeleteArr(pDetData); });

        // 设备序号=0,1,2... 逐一测试设备接口，到失败为止
        SP_DEVICE_INTERFACE_DATA did = { 0 };
        did.cbSize = sizeof(did);
        BOOL bQueryedSucc = TRUE;
        for (DWORD idev = 0; bQueryedSucc; idev++)
        {
            // 枚举符合该GUID的设备接口
            did.cbSize = sizeof(did);
            bQueryedSucc = SetupDiEnumDeviceInterfaces(hDevInfo,// 设备信息集句柄
                NULL,// 不需额外的设备描述
                &lpGuid, // GUID
                idev,// 设备信息集里的设备序号
                &did);// 设备接口信息
            if (!bQueryedSucc)
            {
                int lastError = PCGetLastError();
                if (ERROR_NO_MORE_ITEMS == lastError)
                {
                    break;
                }
                return result.SetSystemFail(lastError);
            }
            if (did.Flags != SPINT_ACTIVE)
            {
                continue;
            }
            
            // 取得该设备接口的细节(设备路径)
            SP_DEVINFO_DATA devdata = { sizeof(SP_DEVINFO_DATA) };
            bQueryedSucc = SetupDiGetInterfaceDeviceDetailA(
                hDevInfo,// 设备信息集句柄
                &did,//设备接口信息
                pDetData,// 设备接口细节(设备路径)
                dwDetDataSize, // 输出缓冲区大小
                NULL,// 不需计算输出缓冲区大小(直接用设定值)
                &devdata);// 额外的设备描述
            if (!bQueryedSucc)
            {
                continue;
            }
            
            if (pszFilter != NULL && pszFilter[0] != 0 && 
                NULL == CPCStrUtil::PCStrStri(pDetData->DevicePath, pszFilter))
            {
                continue;
            }

            // retrieves a specified Plug and Play device property (include friendlyname etc.)
            char friendlyName[MAX_PATH] = { 0 };
            BOOL bSuccess = SetupDiGetDeviceRegistryPropertyA(hDevInfo, &devdata, SPDRP_DEVICEDESC, NULL, (PBYTE)friendlyName, sizeof(friendlyName) - 1, NULL);
            if (!bSuccess) friendlyName[0] = 0;

            // 输出
            umDevices.insert(std::make_pair(pDetData->DevicePath, friendlyName));
        }

#endif
        return result;
    }

protected:
    
#if defined(PCOS_WIN)
	HANDLE		m_hDevice;
	USHORT		m_InputReportByteLength;
	USHORT		m_OutputReportByteLength;	

    HMODULE     m_hHIDDll;
    PC_HidD_GetHidGuid_    PC_HidD_GetHidGuid;
    PC_HidD_GetAttributes_ PC_HidD_GetAttributes;
    PC_HidD_GetSerialNumberString_ PC_HidD_GetSerialNumberString;
    PC_HidD_GetManufacturerString_ PC_HidD_GetManufacturerString;
    PC_HidD_GetProductString_ PC_HidD_GetProductString;
    PC_HidD_SetFeature_ PC_HidD_SetFeature;
    PC_HidD_GetFeature_ PC_HidD_GetFeature;
    PC_HidD_GetIndexedString_ PC_HidD_GetIndexedString;
    PC_HidD_GetPreparsedData_ PC_HidD_GetPreparsedData;
    PC_HidD_FreePreparsedData_ PC_HidD_FreePreparsedData;
    PC_HidP_GetCaps_ PC_HidP_GetCaps;
    PC_HidD_SetNumInputBuffers_ PC_HidD_SetNumInputBuffers;
#endif
};



#endif      //___PCLIB_S_USB_HID___
